package adfc.utils;

/**
 * Created by IntelliJ IDEA.
 * User: aimozg
 * Date: 31.12.11
 * Time: 17:52
 */
public final class Perlin {
    private long seed;
    private int octaves;
    private double persistence;
    private boolean exponentiation;

    public final boolean hasExponentiation() {
        return exponentiation;
    }

    public final void setExponentiation(boolean exponentiation) {
        this.exponentiation = exponentiation;
    }

    public final int getOctaves() {
        return octaves;
    }

    public final double getPersistence() {
        return persistence;
    }

    public final void setOctaves(int octaves) {
        this.octaves = octaves;
    }

    public final void setPersistence(double persistence) {
        this.persistence = persistence;
    }

    public Perlin(long seed, double persistence, int octaves) {
        this.seed = seed;
        this.octaves = octaves;
        this.persistence = persistence;
    }

    public Perlin(long seed) {
        this(seed, 1.0, 1);
    }

    public Perlin() {
        this(System.nanoTime(), 1.0, 1);
    }

    public Perlin(double persistence, int octaves) {
        this(System.nanoTime(), persistence, octaves);
    }

    public final long next(long x) {
        final long multiplier = 0x5DEECE66DL;
        final long addend = 0xBL;
        final long mask = (1L << 48) - 1;
        x = x + seed;
        return (x * multiplier + addend) & mask;
        //return ((x*0x5DEECE66DL)^((x<<17)+0xDEADBEEF))&((1L<<48)-1);
    }

    public final double random1(long x) {
        final long mask = (1L << 26) - 1;
        long val1 = next(x) & mask;
        long val2 = next(val1) & mask;
        return (((long) (val1) << 27) + val2) / (double) (1L << 53);
    }

    public final double random2(long x, long y) {
        long i = x + y + x * y;
        return random1(i);
    }

    public final double random3(long x, long y, long z) {
        long i = x + y + z + x * y + y * z + x * z;
        return random1(i);
    }

    public final double random4(long x, long y, long z, long w) {
        long i = x + y + z + w + x * y + x * z + x * w + y * x + y * w + z * w;
        return random1(i);
    }

    public final static double lint(double y0, double y1, double x) {
        return y0 * x + y1 * (1 - x);
    }

    public final double random1(double x) {
        long x0 = (long) x;
        double xf = 1 + x0 - x;
        return lint(random1(x0), random1(x0 + 1), xf);
    }

    public final double random2(double x, double y) {
        long x0 = (long) x;
        double xf = 1 + x0 - x;
        long y0 = (long) y;
        double yf = 1 + y0 - y;
        double v_y0 = lint(random2(x0, y0), random2(x0 + 1, y0), xf);
        double v_y1 = lint(random2(x0, y0 + 1), random2(x0 + 1, y0 + 1), xf);
        return lint(v_y0, v_y1, yf);
    }

    public final double random3(double x, double y, double z) {
        long x0 = (long) x;
        double xf = 1 + x0 - x;
        long y0 = (long) y;
        double yf = 1 + y0 - y;
        long z0 = (long) z;
        double zf = 1 + z0 - z;
        double v_y0z0 = lint(random3(x0, y0, z0), random3(x0 + 1, y0, z0), xf);
        double v_y0z1 = lint(random3(x0, y0, z0 + 1), random3(x0 + 1, y0, z0 + 1), xf);
        double v_y1z0 = lint(random3(x0, y0 + 1, z0), random3(x0 + 1, y0 + 1, z0), xf);
        double v_y1z1 = lint(random3(x0, y0 + 1, z0 + 1), random3(x0 + 1, y0 + 1, z0 + 1), xf);
        double v_z0 = lint(v_y0z0, v_y1z0, yf);
        double v_z1 = lint(v_y0z1, v_y1z1, yf);
        return lint(v_z0, v_z1, zf);
    }

    public final double random4(double x, double y, double z, double w) {
        long x0 = (long) x;
        double xf = 1 + x0 - x;
        long y0 = (long) y;
        double yf = 1 + y0 - y;
        long z0 = (long) z;
        double zf = 1 + z0 - z;
        long w0 = (long) w;
        double wf = 1 + w0 - w;
        double v_y0z0w0 = lint(random4(x0, y0, z0, w0), random4(x0 + 1, y0, z0, w0), xf);
        double v_y0z0w1 = lint(random4(x0, y0, z0, w0 + 1), random4(x0 + 1, y0, z0, w0 + 1), xf);
        double v_y0z1w0 = lint(random4(x0, y0, z0 + 1, w0), random4(x0 + 1, y0, z0 + 1, w0), xf);
        double v_y0z1w1 = lint(random4(x0, y0, z0 + 1, w0 + 1), random4(x0 + 1, y0, z0 + 1, w0 + 1), xf);
        double v_y1z0w0 = lint(random4(x0, y0 + 1, z0, w0), random4(x0 + 1, y0 + 1, z0, w0), xf);
        double v_y1z0w1 = lint(random4(x0, y0 + 1, z0, w0 + 1), random4(x0 + 1, y0 + 1, z0, w0 + 1), xf);
        double v_y1z1w0 = lint(random4(x0, y0 + 1, z0 + 1, w0), random4(x0 + 1, y0 + 1, z0 + 1, w0), xf);
        double v_y1z1w1 = lint(random4(x0, y0 + 1, z0 + 1, w0 + 1), random4(x0 + 1, y0 + 1, z0 + 1, w0 + 1), xf);
        double v_z0w0 = lint(v_y0z0w0, v_y1z0w0, yf);
        double v_z0w1 = lint(v_y0z0w1, v_y1z0w1, yf);
        double v_z1w0 = lint(v_y0z1w0, v_y1z1w0, yf);
        double v_z1w1 = lint(v_y0z1w1, v_y1z1w1, yf);
        double v_w0 = lint(v_z0w0, v_z1w0, zf);
        double v_w1 = lint(v_z0w1, v_z1w1, zf);
        return lint(v_w0, v_w1, wf);
    }

    private final double join(double[] vals) {
        double val = 0;
        double q = 0;
        for (int i = 0; i < octaves; i++) {
            val = val * persistence + vals[i];
            q = q * persistence + 1;
        }
        if (exponentiation) {
            return (Math.exp(val / q) - (Math.E - 1));
        } else {
            return val / q;
        }
    }

    public final double noise1(double x) {
        double[] vals = new double[octaves];
        for (int i = octaves - 1; i >= 0; i--) {
            vals[i] = random1(x / (1L << i));
        }
        return join(vals);
    }

    public final double noise2(double x, double y) {
        double[] vals = new double[octaves];
        for (int i = octaves - 1; i >= 0; i--) {
            vals[i] = random2(
                    x / (1L << i),
                    y / (1L << i));
        }
        return join(vals);
    }

    public final double noise3(double x, double y, double z) {
        double[] vals = new double[octaves];
        for (int i = octaves - 1; i >= 0; i--) {
            vals[i] = random3(
                    x / (1L << i),
                    y / (1L << i),
                    z / (1L << i));
        }
        return join(vals);
    }

    public final double noise4(double x, double y, double z, double w) {
        double[] vals = new double[octaves];
        for (int i = octaves - 1; i >= 0; i--) {
            vals[i] = random4(
                    x / (1L << i),
                    y / (1L << i),
                    z / (1L << i),
                    w / (1L << i));
        }
        return join(vals);
    }
}